VC PlusPlus:マクロ定義を展開した後のプログラムを確認する方法

提供:yonewiki

VC PlusPlusに戻る

概要

 C++のプリプロセッサ(C プリプロセッサC マクロC プリプロセッサ演算子C ディレクティブ)の記事で、マクロ定義の動きについて細かく説明しましたが、実際のマクロ定義は複雑で、自分が説明した内容が理解できていれば、展開はできるにしても、聞きかじった程度の知識では、全ての置き換えを元に戻せるとは限らないし、もっと複雑なことだったりします。もっと複雑な場合の展開になると創造力がないと難しいです。正直、展開疲れみたいなこともありえます。作った人は楽をしようと思って知恵を振り絞るだけですが、他人が読み解けるかどうかといえば、そうでもないということです。マクロ定義の中でマクロ定義をするという、わりかし深いマクロ定義も存在します。

 例えば以下のようなマクロ定義だとどうでしょう?

#define QT_MANGLE_NAMESPACE(name) name

#define Q_INIT_RESOURCE(name) \
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_ ## name) ();       \
        QT_MANGLE_NAMESPACE(qInitResources_ ## name) (); } while (false)


 Q_INIT_RESOURCE(resource1);
 Q_INIT_RESOURCE(resource2);

 なんじゃこれーっ!ってなったひともいると思います。QtライブラリのQ_INIT_RESOURCEマクロは上記のような定義になっています。ややこしいよね。##はトークン結合演算子です。QInitResources_に引数nameがくっつく感じに変換されます。ようするに前置句がQInitResources_となる名前が作られるということです。\は複数行を一行としてみなすための意味です。


 じゃ、いったんそれで書き直して見ますか。

#define QT_MANGLE_NAMESPACE(name) name

#define Q_INIT_RESOURCE(name) \
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_ ## name) ();       \
        QT_MANGLE_NAMESPACE(qInitResources_ ## name) (); } while (false)


// Q_INIT_RESOURCE(resource1);
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_resource1) ();       
        QT_MANGLE_NAMESPACE(qInitResources_resource1) (); } while (false)

// Q_INIT_RESOURCE(resource2);
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_resource2) ();       
        QT_MANGLE_NAMESPACE(qInitResources_resource2) (); } while (false)


 こうなりました。nameだったところにresource1というキーワードが入りました。マクロは引数一つで二つ以上の場所に展開できるので1行目と2行目両方がかわりました。そしたらQT_MANGLE_NAMESPACEの引数自体が、文字列として再定義されるだけの動きをするのがQT_MANGLE_NAMESPACEの部分なので、それを解読すると、


#define QT_MANGLE_NAMESPACE(name) name

#define Q_INIT_RESOURCE(name) \
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_ ## name) ();       \
        QT_MANGLE_NAMESPACE(qInitResources_ ## name) (); } while (false)


// Q_INIT_RESOURCE(resource1);
    do { extern int qInitResources_resource1();
        qInitResources_resource1 (); } while (false)

// Q_INIT_RESOURCE(resource2);
    do { extern int qInitResources_resource2();
        qInitResources_resource2 (); } while (false)

 となります。整理すると

do {
      extern int qInitResources_resource1();
      qInitResources_resource1();
   }while(false)

do { 
      extern int qInitResources_resource2();
      qInitResources_resource2();
   }while(false)

 と同じです。externがついているので、このファイル以外のどこかで定義されて実体化されているかもしれない外側から呼び出しできるint型のqInitResources_resource1を使うことを宣言しています。それを引数無しで初期化しています。int型の場合は0になります。外から呼び出せる形式でかつ、ここで初期化したいというプログラムになっています。何個もこのような変数を生成するときには、このマクロは便利ですが、これをマクロでやっているということを紐解くのはなかなか手間のかかることだということが、わかったと思います。do{...}while(...)にすることで直接グローバル変数とすることを避けています。do{...}while(false)とすると、{...}の中の処理が1回だけ処理される仕組みになります。


 こういうことを機械的に確認する術があります。知らないよりは知っていた方が良いかもです。かなり深いプリプロッセッサになっている場合はコンパイルにもの凄く時間がかかりますが、ちょっとの我慢です。


マクロ定義展開方法

 VisualStudioの特定のcppファイルのプロパティを変更することでプリプロセッサ処理後のプログラムを吐き出すことが出来ます。併せて、インクルードされているすべての変数、関数、クラス宣言が掌握できます。


1.VisualStudioのソリューションエクスプローラからマクロ定義を展開したいcppファイルを選択して、右クリックして表示されるコンテクストメニューからプロパティを開きます。プロジェクトのプロパティと似たような感じで対象ファイルのプロパティを設定できます。


2.表示されたダイアログで[行番号の前処理の抑制]の欄で値を[はい /EP]にします。


3.VisualStudioのソリューションエクスプローラからマクロ定義を展開したいcppファイルを選択して、右クリックして表示されるコンテクストメニューからコンパイルを選択して、実行します。


 これで出力ダイアログにマクロ展開されたプログラムが表示されます。展開された部分を探すのに疲れる場合もあるかもしれませんが、マクロ展開された部分は必ずあります。根気よく検索で探しましょう。探しきれない場合は一度、かなりユニークな文字列の引数をマクロに設定して試すとよいかもしれません。


1>      do { extern int qInitResources_resource1 (); qInitResources_resource1 (); } while (false);
1>      do { extern int qInitResources_resource2 (); qInitResources_resource2 (); } while (false);

   のようにマクロ展開した結果が出力されます。


 初心者がコードリーディングする時にはオススメだね。コードリーディングってのは誰かが作ったプログラムを読み解く感じの事です。自分が作ったプログラムを後で見返すときにコードリーディングする人もいると思いますけど。

 上記のような結果を出力エディタ(stdout コンパイル・リンク処理の標準出力)にだけ表示するのではなく、ファイルに出力する場合は少しだけ違う設定を使います。


1.VisualStudioのソリューションエクスプローラからマクロ定義を展開したいcppファイルを選択して、右クリックして表示されるコンテクストメニューからプロパティを開きます。プロジェクトのプロパティと似たような感じで対象ファイルのプロパティを設定できます。


2.表示されたダイアログで[ファイルの前処理]の欄で値を[はい /P]にします。


3.VisualStudioのソリューションエクスプローラからマクロ定義を展開したいcppファイルを選択して、右クリックして表示されるコンテクストメニューからコンパイルを選択して、実行します。


 これで出力object(*.obj)ファイルが出力されるフォルダと同じ階層に*.iという名前で前処理の結果が出力されます。object(*.obj)ファイルは生成されません。先ほども記述しましたが、展開された部分を探すのに疲れる場合もあるかもしれませんが、マクロ展開された部分は必ずあります。根気よく検索で探しましょう。探しきれない場合は一度、かなりユニークな文字列の引数をマクロに設定して試すとよいかもしれません。先ほどの出力エディタに表示するのとは異なる内容で、展開中にファイルを行き来するので、ファイルの途中でのファイル離脱時は、その行番号を、行先の行番号とファイル名が表示されます。より、わかりやすいですが、単純に行ったり来たりすることもあるので、冗長に感じる場合もあります。あー、展開処理に満足したら、設定は最初の状態に戻しておいてくださいね。


 いずれにしても、マクロを展開すると、ユーザ定義の部分がかなり減るので、繰り返し記述される無駄な記述(冗長)があるものの理解不能な宣言文が少なくなるので、見やすくはなります。それでも人の作ったクラスや変数があるので、理解することは簡単なことではないです。


 知ってると知らないとでは差がでるところの知識かなと思います。この便利な手法については誰も教えてくれないので、右も左もわからないで、この文章に巡り合えた人はついていると思っていいでしょう。


VC PlusPlusに戻る